#include <sys/socket.h>
#include <sys/time.h>
#include "base/CFdEventMonitor-Inl.h"
#include "utils/sigslot.h"

#include <sys/time.h>
#include <sys/resource.h>

using namespace litevent;
using namespace litevent::util;


inline static long timerElapsedMicroseconds(timeval start, timeval end)
{
    timeval elapsed;
    elapsed.tv_sec = start.tv_sec - end.tv_sec;
    elapsed.tv_usec = start.tv_usec - end.tv_usec;

    if (elapsed.tv_usec < 0)
    {
        elapsed.tv_sec--;
        elapsed.tv_usec += 1000000;
    }

    return elapsed.tv_sec * 1000000L + elapsed.tv_usec;
}

/*
class ClassEvent : public sigslot::has_slots<>
{
public:

    ClassEvent()
    {

    }

    ~ClassEvent()
    {

    }

    void OnReceiveEvent(int fd, void *ctx, int revents)
    {
        // printf("On receive event from fd:%d event:%d", fd, revents);
#if 1
        intptr_t intp = (intptr_t) ctx;
        int idx = intp, widx = idx + 1;
#else
        int idx = (int) ctx, widx = idx + 1;
#endif
        u_char ch;

        count += read(fd, &ch, sizeof (ch));

        if (writes)
        {
            if (widx >= num_pipes)
                widx -= num_pipes;

            write(pipes[2 * widx + 1], "e", 1);

            writes--;
            fired++;
        }
    }

private:


};

ClassEvent evObj;
 */

static int count, writes, fired;
static int *pipes;
static int num_pipes, num_active, num_writes;

CFdEventMonitor *evMonitor = NULL;

void read_cb(int fd, void *ctx, int revents)
{
#if 1
    intptr_t intp = (intptr_t) ctx;
    int idx = intp, widx = idx + 1;
#else
    int idx = (int) ctx, widx = idx + 1;
#endif
    u_char ch;

    count += read(fd, &ch, sizeof (ch));

    if (writes)
    {
        if (widx >= num_pipes)
            widx -= num_pipes;

        write(pipes[2 * widx + 1], "e", 1);

        writes--;
        fired++;
    }
}

long run_once(int type)
{
    static struct timeval ta, ts, te;
    gettimeofday(&ta, NULL);

    int *cp, i, space;

    for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2)
    {
        evMonitor->RemoveEventWatcher(cp[0]);
        evMonitor->SetEventWatcher(cp[0], read_cb, (void*) i, FDEVENT_IN);
        //evMonitor->SetEventWatcher<ClassEvent>(cp[0],(void*) i, FDEVENT_IN , &evObj, & ClassEvent::OnReceiveEvent);
    }

    evMonitor->Loop(EVENT_LOOP_ONCE | EVENT_LOOP_NONBLOCK);

    fired = 0;
    space = num_pipes / num_active;
    space = space * 2;
    for (i = 0; i < num_active; i++, fired++)
        write(pipes[i * space + 1], "e", 1);

    count = 0;
    writes = num_writes;    
    int xcount = 0;

    gettimeofday(&ts, NULL);

    do
    {
        evMonitor->Loop(EVENT_LOOP_ONCE | EVENT_LOOP_NONBLOCK);
        xcount++;
    }
    while (count != fired);

    gettimeofday(&te, NULL);

    //if (xcount != count) fprintf(stderr, "Xcount: %d, Rcount: %d\n", xcount, count);
    
    long elapsed_all  = timerElapsedMicroseconds(te, ta);
    long elapsed_poll = timerElapsedMicroseconds(te, ts);
    fprintf(stdout, "%8ld %8ld\n", elapsed_all , elapsed_poll);

    if(type == 0)
        return elapsed_all;

    return elapsed_poll;
}

// ./litevent -n 1000 -a 100 -m 0 -t 0

/* select = 0, poll = 1, kqueue= 2,epoll = 3, /dev/poll = 4*/
 //sudo ./litevent -n  50 -a 1 -m 3
int main(int argc, char **argv)
{
    int i;
    int *cp;

    num_pipes = 100;
    num_active = 1;
    num_writes = num_pipes;

    EnumEventHandlerType eventType = EnumEventHandlerTypeUnset;

    extern char *optarg;
    int c;
    int typeBechmark = 0;

    while ((c = getopt(argc, argv, "n:a:w:m:t")) != -1)
    {	
        switch (c)
        {
            case 'n':
		num_pipes = atoi(optarg);
		break;

            case 'a':
		num_active = atoi(optarg);
		break;

            case 'w':
		num_writes = atoi(optarg);
		break;

            case 'm':
            {
                int value = atoi(optarg);
                eventType = (EnumEventHandlerType)value;
            }
                break;

            case 't':
                typeBechmark = atoi(optarg);
                break;

            default:
		fprintf(stderr, "Illegal argument \"%c\"\n", c);
		exit(1);
	}
    }

    CLogger::sharedLogger()->WriteFormated("pipes:%d active:%d writes:%d benchmark type:%d",num_pipes, num_active, num_pipes, typeBechmark);

    int pipesLimit = num_pipes * 2 + 50;

#if 1
    
    struct rlimit rl;
    rl.rlim_cur = rl.rlim_max = pipesLimit;
    if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
    {
        perror("setrlimit");
    }
#endif

    CLogger::sharedLogger()->openLog("log.txt", true);

    evMonitor = new CFdEventMonitor(eventType, pipesLimit);

    if (evMonitor->isInitialised() == false)
    {
        perror("Monitor initialization failed");
        exit(1);
    }

    const char **methods = evMonitor->getAvailableMethods();
    for (const char **method = &methods[0]; *method != NULL; method++)
        printf("%s\n", *method);

    printf("Used method:%s\n", evMonitor->getCurrentMethod());

    if ((pipes = (int*) calloc(num_pipes * 2, sizeof (int))) == NULL)
    {
        perror("malloc");
        exit(1);
    }

    for (cp = pipes, i = 0; i < num_pipes; i++, cp += 2)
    {
#ifdef USE_PIPES
        if (pipe(cp) == -1)        
#else
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, cp) == -1)        
#endif
        {
            perror("pipe");
            exit(1);
        }

        IFdEventInterface::makeSocketNonBlock(cp[0]);
        IFdEventInterface::makeSocketNonBlock(cp[1]);
    }

#define BENCHMARK_STEPS 10

    long limits[BENCHMARK_STEPS];

    for (i = 0; i < BENCHMARK_STEPS; i++)
    {
        limits[i] = run_once(typeBechmark);
    }

    long min = limits[0];
    for (i = 0; i < BENCHMARK_STEPS; i++)
        if(min > limits[i])
            min = limits[i];

    CLogger::sharedLogger()->WriteFormated("MINIMUM TIME: %d", min);

    if (evMonitor)
        delete evMonitor;

    delete CLogger::sharedLogger();

    fprintf(stderr, "End\n");

    exit(0);
}



